package com.roshan.web.security.filters;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.util.StringUtils;

import com.roshan.domain.User;
import com.roshan.service.UserService;
import com.roshan.util.constants.ExceptionMessageConstants;
import com.roshan.web.security.context.RoarSecurityContext;
import com.roshan.web.security.domain.RoarAuthenticationToken;
import com.roshan.web.security.domain.RoarMFAAuthenticationToken;
import com.roshan.web.security.domain.type.MFAStatus;
import com.roshan.web.security.exceptions.RoarInsufficientAuthenticationException;
import com.roshan.web.security.exceptions.UserDeactivatedException;
import com.roshan.web.security.exceptions.UserNotFoundException;
import com.roshan.web.security.service.ICookieService;
import com.roshan.web.security.util.CookieUtils;

@SuppressWarnings("nls")
public class RoarAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    private UserService userService;

    @Autowired
    private ICookieService cookieService;

    private @Value("${roar.security.cookie.mfa.name}")
    String mfaCookieName;

    private @Value("${roar.security.cookie.name}")
    String securityCookieName;

    // ------------

    private @Value("${roar.email.notification.enable}")
    Boolean isEmailNotificationEnabled;

    protected RoarAuthenticationProcessingFilter() {
        super(DEFAULT_FILTER_PROCESSES_URL);
    }

    @Override
    protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {

        RoarSecurityContext roarSecurityContext = (RoarSecurityContext) this.cookieService
                .retrieveCookiePersistedObject(this.securityCookieName, request);

        if (roarSecurityContext != null && roarSecurityContext.getAuthentication() != null) {
            this.cookieService.createRoarSecurityContext((RoarAuthenticationToken) roarSecurityContext
                    .getAuthentication(), request, response);
            this.cookieService.createRoarSecurityTimeOutContext(request, response);
            return false;
        }
        request.setAttribute("isEmailNotificationEnabled", this.isEmailNotificationEnabled);
        return super.requiresAuthentication(request, response);

    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        RoarMFAAuthenticationToken mfaToken = null;

        try {

            String username = request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
            String password = request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
            String securityQuestion = request.getParameter(SPRING_SECURITY_FORM_SECURITY_QUESTION_KEY);
            String securityAnswerParameter = request.getParameter(SPRING_SECURITY_FORM_SECURITY_ANSWER_KEY);

            String securityAnswer = securityAnswerParameter != null ? securityAnswerParameter.trim() : null;

            boolean checkPassword = true;
            boolean checkMFA = true;
            String userUri = null;

            User user = checkUsernameExistence(username);
            userUri = user.getUri().toString();

            String mfaCookieContent = (String) this.cookieService.retrieveCookiePersistedObject(CookieUtils
                    .getMFACookieName(this.mfaCookieName, userUri), request);

            if (this.cookieService.isValidMFACookieContent(mfaCookieContent, username, userUri)) {
                checkMFA = false;

            } else {

                mfaToken = (RoarMFAAuthenticationToken) this.cookieService.retrieveCookiePersistedObject(
                        this.mfaCookieName, request);

                if (mfaToken == null || MFAStatus.INIT.equals(mfaToken.getMfaStatus())
                        || !mfaToken.getUserName().equals(username)) {

                    mfaToken = new RoarMFAAuthenticationToken(username, userUri, securityQuestion, MFAStatus.INIT);

                    this.cookieService.createRoarMFASecurityContext(mfaToken, request, response);

                } else if (MFAStatus.READY.equals(mfaToken.getMfaStatus())) {
                    securityQuestion = mfaToken.getSecurityQuestion();
                    checkPassword = false;

                } else if (MFAStatus.AUTHENTICATED.equals(mfaToken.getMfaStatus())) {
                    checkMFA = false;
                }

            }

            // TODO fix this

            List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            authorities.add(new GrantedAuthorityImpl("ROLE_USER"));

            RoarAuthenticationToken authenticationToken = new RoarAuthenticationToken(username, password, authorities,
                    null, null, userUri, securityQuestion, securityAnswer, checkPassword, checkMFA);

            authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
            return getAuthenticationManager().authenticate(authenticationToken);

        } catch (RoarInsufficientAuthenticationException e) {

            if (mfaToken != null) {

                mfaToken.setMfaStatus(MFAStatus.READY);
                mfaToken.setSecurityQuestion(e.getSecurityQuestion());
                mfaToken.setCustomerUri(e.getCustomerUri());

                this.cookieService.createRoarMFASecurityContext(mfaToken, request, response);

            }
            throw e;

        } catch (AuthenticationException e) {

            if (mfaToken != null) {

                if (MFAStatus.READY.equals(mfaToken.getMfaStatus())) {
                    mfaToken.setMfaStatus(MFAStatus.INIT);
                }

                this.cookieService.createRoarMFASecurityContext(mfaToken, request, response);
            }
            throw e;

        }

    }

    private User checkUsernameExistence(String username) {

        if (!StringUtils.hasText(username)) {
            throw new UserNotFoundException(ExceptionMessageConstants.USERNAME_IS_EMPTY);
        }

        User user = this.userService.retrieveUserByUsername(username, true);

        if (user == null) {
            String message = MessageFormat.format(ExceptionMessageConstants.USER_NOT_FOUND_WITH_USERNAME, username);
            throw new UserNotFoundException(message);
        }
        if (!user.isActive()) {
            throw new UserDeactivatedException(ExceptionMessageConstants.USER_HAS_BEEN_DEACTIVATED);
        }
        return user;
    }

    private static final String DEFAULT_FILTER_PROCESSES_URL = "/auth";

    private static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

    private static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private static final String SPRING_SECURITY_FORM_SECURITY_QUESTION_KEY = "securityQuestion";

    private static final String SPRING_SECURITY_FORM_SECURITY_ANSWER_KEY = "securityAnswer";

}
